Skip to content

Conversation

@rkritika1508
Copy link
Collaborator

@rkritika1508 rkritika1508 commented Feb 10, 2026

Summary

Target issue is #33.
Explain the motivation for making this change. What existing problem does the pull request solve?
Currently, we have no way to manage ban list for each NGOs. So, we want to build APIs which manage ban list and allow other NGOs to use them as well.

The following APIs will be added -

  1. POST - /guardrails/ban-lists - Create a ban list
  2. GET - /guardrails/ban-lists - List ban lists (with filters org/proj and domain(health, edu))
  3. GET - /guardrails/ban-lists/{id} - Get the single ban list
  4. PATCH - /guardrails/ban-lists/{id} - Update ban list
  5. DELETE - /guardrails/ban-lists/{id} - Delete ban list
API routes ↓ Schemas (validation/serialization) ↓ CRUD / Repository layer ↓ SQLModel table ↓ Postgres

File-by-File Breakdown

  1. backend/app/alembic/versions/004_added_banlist_config.py - Adds the new table ban_list for migration purposes.
  2. backend/app/models/config/banlist.py - Defines the SQLModel table mapping. Keeps DB logic separate from schemas.
  3. backend/app/schemas/banlist.py‎ - Contains the classes being used by the APIs for banlist configuration.
  4. backend/app/api/routes/banlist_configs.py - Contains all the APIs
  5. backend/app/crud/banlist.py - Contains CRUD operations for BanList.

APIs

  1. /api/v1/guardrails/ban-lists/ - Create banlist
Example of request body
{
  "id": "3f6a9d2e-8c47-4b8a-9f3c-1d2a6e7f4c91",
  "name": "ABC",
  "description": "ABC",
  "banned_words": [
    "sonography"
  ],
  "domain": "healthcare",
  "is_public": true
}

Example of response body
{
  "success": true,
  "data": {
    "id": "3f6a9d2e-8c47-4b8a-9f3c-1d2a6e7f4c91",
    "name": "ABC",
    "description": "ABC",
    "banned_words": [
      "sonography"
    ],
    "domain": "healthcare",
    "is_public": true
  },
  "error": null,
  "metadata": null
}

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Notes

Please add here if any other information is required for the reviewer.

Summary by CodeRabbit

  • New Features

    • Introduces full ban list management (create, list, get, update, delete) with domain filtering and public/private visibility.
  • Database

    • Adds a new ban list table with appropriate indexes and unique constraints.
  • Tests

    • Adds unit and integration tests, seeded test data, and fixtures to validate ban list scenarios.
  • Chores

    • Bumps guardrails-ai dependency to include hub extras and removes example callback timeout env vars.

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds ban-list support: database migration, SQLModel data model, schemas, CRUD layer, FastAPI routes wired into main router, unit and integration tests, test seed data/fixtures, and a dependency bump for guardrails-ai[hub] to >=0.8.0.

Changes

Cohort / File(s) Summary
Migration
backend/app/alembic/versions/004_added_banlist_config.py
New Alembic revision adding banlist table (UUID PK, name, description, banned_words ARRAY, organization_id, project_id, domain, is_public, timestamps), unique constraint on (name, organization_id, project_id), indexes, and downgrade that drops the table.
Model
backend/app/models/config/banlist.py
New BanList SQLModel table definition with fields, defaults, comments, and PostgreSQL ARRAY for banned_words.
Schemas
backend/app/schemas/banlist.py
Adds BanListBase, BanListCreate, BanListUpdate (optional fields), and BanListResponse (includes id) for request/response models.
CRUD
backend/app/crud/banlist.py
New BanListCrud with create/get/list/update/delete, ownership checks (check_owner), IntegrityError handling mapping to 400, timestamp updates, and exported banlist_crud instance.
API Routes
backend/app/api/routes/banlist_configs.py
New router /guardrails/ban-lists with POST, GET (list), GET/{id}, PATCH/{id}, DELETE/{id}; uses TenantDep, AuthDep, SessionDep, and delegates to banlist_crud, returning APIResponse wrappers.
Router Registration
backend/app/api/main.py
Imports and registers banlist_configs.router into API router order; moves utils.router to the end.
Tests — Unit
backend/app/tests/test_banlist_configs.py
Unit tests for route handlers mocking banlist_crud covering create/list/get/update/delete behaviors and responses.
Tests — Integration & Fixtures
backend/app/tests/test_banlists_integration.py, backend/app/tests/conftest.py, backend/app/tests/seed_data.py, backend/app/tests/seed_data.json, backend/app/tests/seed_data.py
Integration tests exercising full ban-list API flows; new seed data, seed fixtures (seed_db, seed_test_data), and helpers for creating sample objects and seeding DB for integration tests.
Other Tests Updates
backend/app/tests/test_validator_configs.py, backend/app/tests/test_validator_configs_integration.py
Switched validator tests to use new seed-data helpers and seeded fixtures; updated test signatures to include seed_db where applicable.
Tests — Helpers
backend/app/tests/test_banlists_integration.py, backend/app/tests/test_banlist_configs.py
Added/updated helpers and mocks to work with new schemas and CRUD behavior.
Dependency
backend/pyproject.toml
Bumps guardrails-ai dependency to guardrails-ai[hub]>=0.8.0 (adds hub extras and raises minimum version).
Env examples
.env.example, .env.test.example
Removed CALLBACK_CONNECT_TIMEOUT and CALLBACK_READ_TIMEOUT variables and related comment lines from example env files.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as "FastAPI (/guardrails/ban-lists)"
    participant CRUD as "banlist_crud"
    participant DB as "Database (banlist table)"

    Client->>API: POST /guardrails/ban-lists (payload, tenant headers)
    API->>CRUD: create(data, organization_id, project_id)
    CRUD->>DB: INSERT banlist row
    DB-->>CRUD: return created row
    CRUD-->>API: return BanList object
    API-->>Client: 201 APIResponse(BanList)

    Client->>API: GET /guardrails/ban-lists?domain=...
    API->>CRUD: list(organization_id, project_id, domain)
    CRUD->>DB: SELECT ... WHERE org_id/ proj_id OR is_public
    DB-->>CRUD: return rows
    CRUD-->>API: list[BanList]
    API-->>Client: 200 APIResponse(list)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • nishika26
  • dennyabrain
  • AkhileshNegi

Poem

🐰 I hopped a migration through the night,
I seeded data, made routes polite,
Words are blocked, domains kept neat,
CRUD and tests now sing in beat,
A little rabbit cheers: all set — delight! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[Guardrails] Banlist API' directly and accurately summarizes the main change—adding a complete banlist API with CRUD endpoints for managing ban lists.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/banlist-management

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rkritika1508 rkritika1508 linked an issue Feb 10, 2026 that may be closed by this pull request
@rkritika1508 rkritika1508 changed the title Feat/banlist management [Guardrails] Banlist API Feb 10, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@backend/app/alembic/versions/004_added_banlist_config.py`:
- Around line 21-41: The migration creates the ban_list table without a
uniqueness constraint, so the IntegrityError handling in BanListCrud.create and
BanListCrud.update is dead code; either add a unique constraint to enforce
unique (name, organization_id, project_id) or remove that exception handling. To
fix by enforcing uniqueness, update the upgrade() in the
004_added_banlist_config.py migration to add a UniqueConstraint on ('name',
'organization_id', 'project_id') (e.g., include
sa.UniqueConstraint('name','organization_id','project_id') in op.create_table or
call op.create_unique_constraint after table creation) and add the corresponding
drop (op.drop_constraint) in downgrade(); reference the ban_list table and
BanListCrud.create / BanListCrud.update so the CRUD IntegrityError handling
becomes meaningful.

In `@backend/app/api/routes/banlist_configs.py`:
- Around line 64-78: The update_banlist and delete_banlist routes allow mutating
public ban lists because banlist_crud.get() skips ownership for obj.is_public
and banlist_crud.update/delete do not verify owner; add an explicit ownership
check in both route handlers after fetching obj (in update_banlist and
delete_banlist) that compares the object's owner (obj.organization_id or
obj.owner_id) against the caller's organization (use the AuthDep value currently
ignored in the signature or the organization_id parameter) and raise an
authorization error (HTTP 403) if they differ; ensure the check runs for
mutating operations on public lists so only the owning organization can update
or delete, then proceed to call banlist_crud.update or banlist_crud.delete when
the ownership check passes.
- Around line 24-32: create_banlist currently calls banlist_crud.create without
verifying the caller's tenant membership; before invoking banlist_crud.create,
extract the authenticated user's identity from the AuthDep (or accept/extend
AuthDep to return a user object), then verify that user belongs to the provided
organization_id and project_id (either by calling the existing check_owner()
helper in the CRUD layer or by querying the membership via the SessionDep), and
only proceed to call banlist_crud.create if that validation succeeds; if
validation fails, return an appropriate authorization error instead of creating
the banlist.

In `@backend/app/crud/banlist.py`:
- Around line 14-38: The create method for BanList currently only catches
IntegrityError, so it must mirror the update/delete behavior: wrap
session.commit() in a try block that catches IntegrityError (raising the 400
HTTPException) and also catches a generic Exception to session.rollback() and
re-raise as an HTTPException(500) (or propagate after rollback) to ensure the DB
session is not left broken; update the create function (BanList.create, where
session.commit() is called after session.add(obj) and before
session.refresh(obj)) to perform the same rollback-on-any-exception logic used
by update and delete.

In `@backend/app/models/config/banlist.py`:
- Around line 26-29: The BanList model's description field is declared
Optional[str] but marked nullable=False, causing a mismatch; change the type
annotation on the description field from Optional[str] to str (i.e., make it
required) so it aligns with sa_column nullable=False and the ORM won't try to
insert NULL, or alternatively set sa_column_kwargs nullable=True if you want to
allow nulls—update the declaration for the description Field in the BanList
model accordingly (adjust the type annotation and/or the sa_column_kwargs
nullable flag).
- Around line 53-58: The Field declaration for domain is inconsistent: it's
typed as str but given default=None while sa_column_kwargs sets nullable=False;
fix by making domain required—remove default=None from the Field for the domain
attribute in banlist.py (keep type str and nullable=False) so the
Pydantic/SQLAlchemy model enforces a value on creation; update any code that
instantiates the model to provide domain where missing.

In `@backend/app/schemas/banlist.py`:
- Around line 8-9: BanListBase currently sets model_config =
ConfigDict(extra="allow"), which lets BanListCreate accept arbitrary request
fields that then flow into the CRUD create path (see
BanList(**data.model_dump(), ...) in the create method); change BanListBase to
forbid extra fields (use ConfigDict(extra="forbid") or remove the model_config
so default behavior applies) so unexpected fields are rejected at validation
time and cannot reach the create logic.
🧹 Nitpick comments (13)
backend/pyproject.toml (1)

25-25: Consider adding an upper-bound pin for guardrails-ai.

Most dependencies in this file use an upper bound (e.g., <X.0.0), but guardrails-ai[hub]>=0.8.0 is open-ended. A major version bump could introduce breaking changes. Consider constraining it, e.g., guardrails-ai[hub]>=0.8.0,<1.0.0.

backend/app/tests/test_banlist_configs.py (2)

113-125: Assert that crud.delete was actually called.

test_delete_success only checks result.success but never verifies that crud.delete was invoked. Without this, the test would pass even if the delete call were accidentally removed from the route handler.

Proposed fix
         result = delete_banlist(
             id=TEST_ID,
             organization_id=TEST_ORGANIZATION_ID,
             project_id=TEST_PROJECT_ID,
             session=mock_session,
             _=None,
         )

         assert result.success is True
+        crud.delete.assert_called_once_with(mock_session, sample_banlist)

53-65: Consider asserting crud.create was called with the expected arguments.

Same pattern applies to the other tests — verifying that the CRUD method was called with the correct arguments strengthens confidence that the route is wiring parameters correctly, not just returning a canned mock.

backend/app/schemas/banlist.py (1)

1-3: Remove unused imports: datetime and List.

Neither datetime nor List are used in this file. Ruff also flags List (UP035).

Proposed fix
 from uuid import UUID
-from datetime import datetime
-from typing import List, Optional
+from typing import Optional
backend/app/tests/test_banlists_integration.py (2)

65-69: Method name list shadows the Python built-in.

Consider renaming to list_banlists or list_all to avoid shadowing builtins.list. It won't break anything here, but it can cause subtle issues if someone later uses list(...) inside this class.


81-98: Consider adding a test for duplicate creation (IntegrityError path).

The CRUD layer handles IntegrityError and returns a 400. This error path isn't exercised by any integration test. A test creating the same ban list twice would cover it.

backend/app/alembic/versions/004_added_banlist_config.py (2)

31-32: created_at and updated_at lack server_default.

If rows are ever inserted outside the ORM (manual SQL, other services, data migrations), these columns will fail the NOT NULL constraint. Consider adding server_default=sa.func.now() for resilience.


42-43: Downgrade should drop indexes explicitly before dropping the table.

While PostgreSQL automatically drops indexes when the table is dropped, being explicit in the downgrade makes the migration symmetric and portable across databases.

backend/app/models/config/banlist.py (1)

1-3: Remove unused import List.

List from typing is imported but not used; the code uses the built-in list[str] syntax throughout. Ruff also flags this (UP035).

Proposed fix
 from datetime import datetime
-from typing import List, Optional
+from typing import Optional
 from uuid import UUID, uuid4
backend/app/crud/banlist.py (3)

57-75: No pagination on the list query — may return unbounded results.

The list method returns all matching records (own + all public from every org). As the number of public ban lists grows, this could become a performance issue. Consider adding limit/offset (or cursor-based) pagination.


2-2: Use built-in list instead of typing.List.

typing.List is deprecated since Python 3.9. The built-in list is already used elsewhere in this codebase (e.g., list[str] in the model). This aligns with the Ruff UP035 hint.

Proposed fix
-from typing import List, Optional
+from typing import Optional

And on line 63:

-    ) -> List[BanList]:
+    ) -> list[BanList]:

118-118: Add trailing newline at end of file.

Per Ruff W292 and POSIX convention.

backend/app/api/routes/banlist_configs.py (1)

34-46: List endpoint looks correct, but inherits the pagination concern from the CRUD layer.

As noted in the CRUD review, the list method returns unbounded results. Consider adding limit/offset query params here to support pagination.

Comment on lines 24 to 32
def create_banlist(
payload: BanListCreate,
session: SessionDep,
organization_id: int,
project_id: int,
_: AuthDep,
):
response_model = banlist_crud.create(session, payload, organization_id, project_id)
return APIResponse.success_response(data=response_model)
Copy link

@coderabbitai coderabbitai bot Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.py" | xargs grep -l "AuthDep" | head -20

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 232


🏁 Script executed:

cat -n ./backend/app/api/deps.py

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 1438


🏁 Script executed:

cat -n ./backend/app/api/routes/banlist_configs.py

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 3047


🏁 Script executed:

find . -type f -name "*.py" -path "*/crud/*" | xargs grep -l "banlist" | head -5

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 107


🏁 Script executed:

cat -n ./backend/app/crud/banlist.py

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 3936


🏁 Script executed:

find . -type f -name "*.py" | xargs grep -l "middleware\|tenant\|organization_id" | grep -E "middleware|main|app\.py" | head -10

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 99


🏁 Script executed:

cat -n ./backend/app/main.py

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 1365


🏁 Script executed:

find . -type f -name "*.py" -path "*/middleware*" -o -name "middleware.py" | head -5

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 110


🏁 Script executed:

cat -n ./backend/app/core/middleware.py

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 883


Apply tenant isolation validation in create_banlist() to prevent privilege escalation.

The create() CRUD method accepts organization_id and project_id as query parameters without validating that the authenticated user has access to them. Any authenticated user can create banlists for arbitrary organizations by passing different query parameter values. While AuthDep validates the bearer token, it provides no user context or tenant affiliation. check_owner() exists in the CRUD layer but is only invoked for reads/updates/deletes on public resources—not for creation.

Add validation before calling banlist_crud.create() to ensure the authenticated user belongs to the specified organization and project, or extract user context from the bearer token to enforce this check.

🤖 Prompt for AI Agents
In `@backend/app/api/routes/banlist_configs.py` around lines 24 - 32,
create_banlist currently calls banlist_crud.create without verifying the
caller's tenant membership; before invoking banlist_crud.create, extract the
authenticated user's identity from the AuthDep (or accept/extend AuthDep to
return a user object), then verify that user belongs to the provided
organization_id and project_id (either by calling the existing check_owner()
helper in the CRUD layer or by querying the membership via the SessionDep), and
only proceed to call banlist_crud.create if that validation succeeds; if
validation fails, return an appropriate authorization error instead of creating
the banlist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rkritika1508 check this comment

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AkhileshNegi a bit confused. When kaapi-guardrails makes an API call to kaapi (/login/access-token) with the auth-token, we assumed kaapi will do the authentication and authorization work. We don't store user information to be able to check whether this user belongs to this org or project.

Comment on lines 64 to 78
@router.patch(
"/{id}",
response_model=APIResponse[BanListResponse]
)
def update_banlist(
id: UUID,
organization_id: int,
project_id: int,
payload: BanListUpdate,
session: SessionDep,
_: AuthDep,
):
obj = banlist_crud.get(session, id, organization_id, project_id)
response_model = banlist_crud.update(session, obj=obj, data=payload)
return APIResponse.success_response(data=response_model)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Any authenticated user can update (and delete) public ban lists they don't own.

The update_banlist route calls banlist_crud.get() to fetch the object, but get() skips the ownership check when obj.is_public is True. The update() CRUD method itself performs no ownership verification either. This means any authenticated user can modify any public ban list created by another organization.

The same issue applies to delete_banlist (lines 80–93).

Proposed fix — enforce ownership on mutating operations

Either add an explicit ownership check in the route before mutating:

 def update_banlist(
     id: UUID,
     organization_id: int,
     project_id: int,
     payload: BanListUpdate,
     session: SessionDep,
     _: AuthDep,
 ):
     obj = banlist_crud.get(session, id, organization_id, project_id)
+    banlist_crud.check_owner(obj, organization_id, project_id)
     response_model = banlist_crud.update(session, obj=obj, data=payload)
     return APIResponse.success_response(data=response_model)

And similarly for delete_banlist:

 def delete_banlist(
     id: UUID,
     organization_id: int,
     project_id: int,
     session: SessionDep,
     _: AuthDep,
 ):
     obj = banlist_crud.get(session, id, organization_id, project_id)
+    banlist_crud.check_owner(obj, organization_id, project_id)
     banlist_crud.delete(session, obj)
     return APIResponse.success_response(data={"message": "Banlist deleted successfully"})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@router.patch(
"/{id}",
response_model=APIResponse[BanListResponse]
)
def update_banlist(
id: UUID,
organization_id: int,
project_id: int,
payload: BanListUpdate,
session: SessionDep,
_: AuthDep,
):
obj = banlist_crud.get(session, id, organization_id, project_id)
response_model = banlist_crud.update(session, obj=obj, data=payload)
return APIResponse.success_response(data=response_model)
`@router.patch`(
"/{id}",
response_model=APIResponse[BanListResponse]
)
def update_banlist(
id: UUID,
organization_id: int,
project_id: int,
payload: BanListUpdate,
session: SessionDep,
_: AuthDep,
):
obj = banlist_crud.get(session, id, organization_id, project_id)
banlist_crud.check_owner(obj, organization_id, project_id)
response_model = banlist_crud.update(session, obj=obj, data=payload)
return APIResponse.success_response(data=response_model)
🤖 Prompt for AI Agents
In `@backend/app/api/routes/banlist_configs.py` around lines 64 - 78, The
update_banlist and delete_banlist routes allow mutating public ban lists because
banlist_crud.get() skips ownership for obj.is_public and
banlist_crud.update/delete do not verify owner; add an explicit ownership check
in both route handlers after fetching obj (in update_banlist and delete_banlist)
that compares the object's owner (obj.organization_id or obj.owner_id) against
the caller's organization (use the AuthDep value currently ignored in the
signature or the organization_id parameter) and raise an authorization error
(HTTP 403) if they differ; ensure the check runs for mutating operations on
public lists so only the owning organization can update or delete, then proceed
to call banlist_crud.update or banlist_crud.delete when the ownership check
passes.

@rkritika1508 rkritika1508 self-assigned this Feb 10, 2026
@rkritika1508 rkritika1508 added enhancement New feature or request ready-for-review labels Feb 10, 2026
@rkritika1508
Copy link
Collaborator Author

@CodeRabbit full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@backend/app/api/deps.py`:
- Line 70: Add a trailing newline at the end of the file so the file ends with a
newline character; specifically update the file containing the TenantDep
definition (Annotated[Tenant, Depends(get_tenant)]) to ensure there is a newline
after the final line to satisfy W292.

In `@backend/app/tests/test_banlists_integration.py`:
- Line 14: The tests currently build a DEFAULT_QUERY string and append
?organization_id=...&project_id=... to helper request URLs, but the application
now uses TenantDep headers (X-Org-Id / X-Project-Id) so those query params are
dead; remove DEFAULT_QUERY and any usage of the query string in helper functions
and test calls in test_banlists_integration.py (and the helper methods
referenced there) and ensure requests rely solely on the TenantDep headers
already being set (X-Org-Id, X-Project-Id) to derive tenant context.
- Around line 242-251: The test test_delete_wrong_owner currently sends a stale
query string in the DELETE call
(f"{BASE_URL}{ban_id}/?organization_id=999&project_id=999") which is ignored;
remove the query params and call integration_client.delete with the resource
path only (e.g. f"{BASE_URL}{ban_id}/") and continue to pass the owner headers
via self._headers(999, 999) so the request relies on headers for auth/ownership.
🧹 Nitpick comments (1)
backend/app/api/deps.py (1)

48-67: Falsy check on int headers will reject valid 0 IDs and is partially redundant.

Header(...) already makes the headers required — FastAPI returns 422 if they're missing or non-integer. The if not x_org_id check on line 58 only additionally rejects 0. If org/project IDs are always positive, consider making the intent explicit with <= 0; if 0 could be valid, this is a bug.

Suggested clarification
-    if not x_org_id or not x_project_id:
+    if x_org_id <= 0 or x_project_id <= 0:
         raise HTTPException(
             status_code=status.HTTP_400_BAD_REQUEST,
             detail="Missing tenant headers",
         )

Comment on lines +26 to +27
sa.Column('organization_id', sa.Integer(), nullable=False),
sa.Column('project_id', sa.Integer(), nullable=False),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to this we might need to introduce them to other table as well

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which table?

Comment on lines 24 to 32
def create_banlist(
payload: BanListCreate,
session: SessionDep,
organization_id: int,
project_id: int,
_: AuthDep,
):
response_model = banlist_crud.create(session, payload, organization_id, project_id)
return APIResponse.success_response(data=response_model)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rkritika1508 check this comment

self,
session: Session,
id: UUID,
organization_id: int,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just pass the schema you made containing both org id and project id, and then use it later this way , schema.organization_id, schema.project_id

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, didn't get you

@rkritika1508 rkritika1508 force-pushed the feat/banlist-management branch from 0adff7e to 8c0b7de Compare February 10, 2026 11:00
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/tests/test_validator_configs_integration.py (1)

59-61: ⚠️ Potential issue | 🟠 Major

Inconsistent URL construction — update_validator is missing the trailing slash.

get_validator (line 47) and delete_validator (line 65) use {BASE_URL}{validator_id}/{DEFAULT_QUERY_PARAMS} (with trailing /), but update_validator uses {BASE_URL}{validator_id}{DEFAULT_QUERY_PARAMS} (no trailing /). This produces a different path that may trigger a 307 redirect in FastAPI, potentially causing the PATCH body to be dropped.

Fix: add trailing slash
     def update_validator(self, client, validator_id, payload):
         """Helper to update a validator."""
-        return client.patch(f"{BASE_URL}{validator_id}{DEFAULT_QUERY_PARAMS}", json=payload)
+        return client.patch(f"{BASE_URL}{validator_id}/{DEFAULT_QUERY_PARAMS}", json=payload)
🤖 Fix all issues with AI agents
In `@backend/app/tests/test_banlists_integration.py`:
- Line 5: The clear_database fixture is importing the production engine (engine)
instead of using the test_engine provided by conftest.py; update clear_database
to use the conftest test_engine (or move the clear_database fixture into
conftest alongside other DB fixtures) so it runs against the test database
instance; locate the clear_database fixture in test_banlists_integration.py (and
similarly in test_validator_configs_integration.py) and replace the engine
reference with test_engine (or relocate the entire fixture into conftest.py) to
ensure the fixture uses the same test_engine configuration as other DB fixtures.

In `@backend/app/tests/test_validator_configs_integration.py`:
- Around line 5-6: The clear_database fixture is importing and using the
production engine from app.core.db instead of the test_engine set up in
conftest; update the import in test_validator_configs_integration.py to use
test_engine (the same symbol used in conftest) and ensure any references inside
the clear_database fixture call/operate on test_engine rather than engine so the
fixture clears the test database during integration tests.
🧹 Nitpick comments (2)
backend/app/tests/conftest.py (1)

86-93: Redundant deletes in seed_dbclean_db (autouse) already clears all tables.

Since clean_db is autouse=True and runs before every test function (clearing all tables), the explicit delete(BanList) / delete(ValidatorConfig) in seed_db are redundant. You can simplify to just seed and yield.

Simplified seed_db
 `@pytest.fixture`(scope="function")
 def seed_db():
     with Session(test_engine) as session:
-        session.exec(delete(BanList))
-        session.exec(delete(ValidatorConfig))
-        session.commit()
         seed_test_data(session)
         yield
backend/app/tests/test_validator_configs_integration.py (1)

49-57: list_validators rebuilds DEFAULT_QUERY_PARAMS inline instead of reusing the constant.

Lines 51-54 duplicate the same string that DEFAULT_QUERY_PARAMS (line 17-20) already defines.

DRY fix
     def list_validators(self, client, **query_params):
         """Helper to list validators with optional filters."""
-        params_str = (
-            f"?organization_id={VALIDATOR_INTEGRATION_ORGANIZATION_ID}"
-            f"&project_id={VALIDATOR_INTEGRATION_PROJECT_ID}"
-        )
+        params_str = DEFAULT_QUERY_PARAMS
         if query_params:
             params_str += "&" + "&".join(f"{k}={v}" for k, v in query_params.items())
         return client.get(f"{BASE_URL}{params_str}")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request ready-for-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Ban List Management API changes

4 participants